package errors

import (
	"fmt"
	"net/http"
	"sync"
)

// _unknownCode 是未注册的错误码，禁止用户注册。
var _unknownCode = defaultCode{
	Code:       -1,
	HTTPStatus: -1,
	Msg:        "unregistered error code",
	Ref:        "https://gitee.com/baihr/iam/tree/master/pkg/errors",
}

// _internalErrorCode 是为内部错误保留的错误码，未显式设置错误码时使用，禁止用户
// 注册。
var _internalErrorCode = defaultCode{
	Code:       0,
	HTTPStatus: http.StatusInternalServerError,
	Msg:        "internal error",
	Ref:        "https://gitee.com/baihr/iam/tree/master/pkg/errors",
}

// 关于接口方法的命名，倾向于使用表意动词（类似 get/Get ）作为前缀，否则对象属性
// 命名时，为避免与方法名称重复只能选择一些不恰当的名称，例外情况：
//     - 满足一些特殊的约定，比如 String 方法；
//     - 保持已有的风格；

// Coder 定义了错误码对象的行为。
type Coder interface {
	// GetCode 返回错误码的整型数。
	GetCode() int
	// GetHTTPStatus 返回关联的 HTTP 响应码。
	GetHTTPStatus() int
	// String 返回错误码描述。
	String() string
	// GetReference 返回错误码的参考信息，例如 URL 。
	GetReference() string
}

// defaultCoder 是内置的错误码对象。
type defaultCode struct {
	// C 是错误码的整型代码。
	Code int
	// HTTPC 是关联错误码的 HTTP 状态码。
	HTTPStatus int
	// Msg 是错误码相关的描述。
	Msg string
	// Ref 是错误码相关的参考信息。
	Ref string
}

var _ Coder = &defaultCode{}

// Code 返回错误码。
func (d defaultCode) GetCode() int { return d.Code }

// HTTPStatus 返回关联的 HTTP 响应码，如果响应码为 0 则返回 500 。
func (d defaultCode) GetHTTPStatus() int {
	// 响应码 0 ，代表本地响应成功。
	if d.HTTPStatus == 0 {
		return 500
	}

	return d.HTTPStatus
}

// String 返回描述信息。
func (d defaultCode) String() string { return d.Msg }

// Reference 返回参考信息。
func (d defaultCode) GetReference() string { return d.Ref }

var (
	// _codes 是错误码注册表。
	_codes = newCodes()
	mu     sync.Mutex
)

// [避免使用 init()](https://github.com/xxjwxc/uber_go_guide_cn#%E9%81%BF%E5%85%8D%E4%BD%BF%E7%94%A8-init)

// newCodes 初始化错误码注册表。
func newCodes() map[int]Coder {
	var cs = map[int]Coder{}
	cs[_unknownCode.GetCode()] = _unknownCode
	cs[_internalErrorCode.GetCode()] = _internalErrorCode

	return cs
}

// Register 注册用户定义的错误码，错误码必须大于 0 ，注册重复错误码时覆盖。建议
// 使用 MustRegister ，它将在重复注册时 panic 。
func Register(cr Coder) {
	// 小于等于 0 的错误码为内部保留。
	if cr.GetCode() <= 0 {
		panic("code must be greater than 0")
	}

	mu.Lock()
	defer mu.Unlock()

	_codes[cr.GetCode()] = cr
}

// Register 注册用户定义的错误码，错误码必须大于 0 ，重复注册时 panic 。
func MustRegister(cr Coder) {
	if cr.GetCode() <= 0 {
		panic("code must be greater than 0")
	}

	mu.Lock()
	defer mu.Unlock()

	if _, ok := _codes[cr.GetCode()]; ok {
		panic(fmt.Sprintf("code: %d already exist", cr.GetCode()))
	}

	_codes[cr.GetCode()] = cr
}

// codeWrapr 定义了错误码包装的行为。
type codeWrapr interface {
	// Code 返回错误码。
	Code() int
}

// PaseCoder 根据传入错误的错误码返回错误码对象。
func PaseCoder(err error) Coder {
	if err == nil {
		return nil
	}

	if we, ok := err.(codeWrapr); ok {
		if c, ok := _codes[we.Code()]; ok {
			return c
		}
	}

	return &_unknownCode
}

// IsCode 在错误链中匹配给定的错误码，匹配到则返回 true 。
func IsCode(err error, code int) bool {
	if err == nil {
		return false
	}

	if we, ok := err.(codeWrapr); ok {
		if we.Code() == code {
			return true
		}

		if wwe, ok := we.(causer); ok {
			return IsCode(wwe.Cause(), code)
		}

		return false
	}

	return false
}
